#include "sys.h"
#include "net.h"
#include "log.h"
#include "packet.h"

#define UINT8_PTR(x) ((uint8_t*)x)

#define DEFAULT_TTL 64

static uint16_t _ip_checksum(uint8_t* ptr, int size)
{
    int cksum = 0;
    int index = 0;

    *(ptr + 10) = 0;
    *(ptr + 11) = 0;

    if (size % 2 != 0)
        return 0;

    while (index < size) {
        cksum += *(ptr + index + 1);
        cksum += *(ptr + index) << 8;

        index += 2;
    }

    while (cksum > 0xffff) {
        cksum = (cksum >> 16) + (cksum & 0xffff);
    }
    return ~cksum;
}
static uint16_t _icmp_check_sum(uint16_t* addr, int nleft)
{
    unsigned sum = 0;
    while (nleft > 1) {
        sum += *addr++;
        nleft -= 2;
    }

    if (nleft == 1) {
        if (BB_LITTLE_ENDIAN) {
            sum += *(uint16_t*)addr;
        } else
            sum += *(uint16_t*)addr << 8;
    }

    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);

    return (uint16_t)~sum;
}

static uint16_t _tcp_udp_checksum(uint32_t saddr, uint32_t daddr, uint16_t len,
    uint8_t protocol, const uint8_t* data)
{
    uint16_t proto = 0;
    uint32_t cksum = 0;
    int count = 0;

    proto |= protocol;
    cksum = (saddr >> 16) + (saddr & 0xffff) + (daddr >> 16) + (daddr & 0xffff) + len + proto;

    count = ntohs(len);
    while (count > 1) {
        cksum += *(uint16_t*)data;
        data += 2;
        count -= 2;
    }
    if (count > 0) {
        cksum += *(uint16_t*)data;
    }
    while (cksum >> 16) {
        cksum = (cksum & 0xffff) + (cksum >> 16);
    }

    return ~cksum;
}

static int net_checksum(Packet_t* packet)
{
    struct iphdr* ip_hdr = packet->ip_hdr;

    assert(ip_hdr != NULL);

    if (packet->ip_hdr) {
        ip_hdr->check = 0;
        ip_hdr->check = htons(_ip_checksum((uint8_t*)ip_hdr, ip_hdr->ihl * 4));
    }

    if (packet->icmp_hdr) {
        struct icmp* icmp_hdr = packet->icmp_hdr;
        icmp_hdr->icmp_cksum = 0;
        icmp_hdr->icmp_cksum = _icmp_check_sum((uint16_t*)icmp_hdr, sizeof(struct icmp));
        return 1;
    }

    if (packet->udp_hdr) {
        struct udphdr* udp_hdr = packet->udp_hdr;
        udp_hdr->check = 0;
        udp_hdr->check = _tcp_udp_checksum(ip_hdr->saddr, ip_hdr->daddr, udp_hdr->len,
            ip_hdr->protocol, (uint8_t*)udp_hdr);
    }
    /* if(packet->tcp_hdr){
        struct tcphdr * tcp_hdr = packet->tcp_hdr;
        int tcp_all_len = (ip_hdr->tot_len - ip_hdr->ihl * 4);
        tcp_hdr->check = 0;
        tcp_hdr->check = _tcp_udp_checksum(ip_hdr->saddr, ip_hdr->daddr, \
                                            tcp_all_len, \
                                            ip_hdr->protocol, tcp_hdr);
    }*/
    return 1;
}
int net_stack(Packet_t* in, Packet_t* out)
{
    uint8_t* next_hdr = UINT8_PTR(out->buf);
    if (in->eth_hdr) {
        out->eth_hdr = (struct ethhdr*)next_hdr;
        memcpy(out->eth_hdr->h_dest, in->eth_hdr->h_source, ETH_ALEN);
        memcpy(out->eth_hdr->h_source, in->eth_hdr->h_dest, ETH_ALEN);
        out->eth_hdr->h_proto = in->eth_hdr->h_proto;
        next_hdr = UINT8_PTR(out->eth_hdr) + sizeof(struct ethhdr);
        out->len += sizeof(struct ethhdr);
    }
    if (in->vlan_hdr) {
        out->vlan_hdr = (struct vlanhdr*)next_hdr;
        memcpy(out->vlan_hdr, in->vlan_hdr, sizeof(struct vlanhdr));
        next_hdr = UINT8_PTR(out->vlan_hdr) + sizeof(struct vlanhdr);
        out->len += sizeof(struct vlanhdr);
    }
    if (in->ppp_hdr) {
        out->ppp_hdr = (struct ppphdr*)next_hdr;
        memcpy(out->ppp_hdr, in->ppp_hdr, sizeof(struct ppphdr));
        next_hdr = UINT8_PTR(out->ppp_hdr) + sizeof(struct ppphdr);
        out->len += sizeof(struct ppphdr);
    }
    if (in->ip_hdr) {
        struct iphdr *in_iphdr, *out_iphdr;
        out->ip_hdr = (struct iphdr*)next_hdr;
        in_iphdr = in->ip_hdr;
        out_iphdr = out->ip_hdr;
        out_iphdr->version = in_iphdr->version;
        out_iphdr->ihl = in_iphdr->ihl;
        out_iphdr->tos = in_iphdr->tos;
        out_iphdr->tot_len = 0;
        out_iphdr->id = in_iphdr->id;
        out_iphdr->frag_off = in_iphdr->frag_off;
        out_iphdr->ttl = DEFAULT_TTL;
        out_iphdr->protocol = in_iphdr->protocol;
        out_iphdr->check = 0;
        out_iphdr->saddr = in_iphdr->daddr;
        out_iphdr->daddr = in_iphdr->saddr;
        next_hdr = UINT8_PTR(out->ip_hdr) + sizeof(struct iphdr);
        out->len += out_iphdr->ihl * 4;
    }
    if (in->icmp_hdr) {
        out->icmp_hdr = (struct icmp*)next_hdr;
        out->data = out->icmp_hdr;
        return 1;
    }
    if (in->udp_hdr) {
        out->udp_hdr = (struct udphdr*)next_hdr;
        out->udp_hdr->source = in->udp_hdr->dest;
        out->udp_hdr->dest = in->udp_hdr->source;
        out->udp_hdr->len = 0;
        out->udp_hdr->check = 0;
        next_hdr = UINT8_PTR(out->udp_hdr) + sizeof(struct udphdr);
        out->len += sizeof(struct udphdr);
    }
    if (in->tcp_hdr) {
    }
    out->data = next_hdr;
    return 1;
}

static int net_fill_user_data(Packet_t* packet, uint8_t* user_data, int len)
{
    if (packet->udp_hdr) {
        packet->udp_hdr->len = htons(sizeof(struct udphdr) + len);
    }
    if (packet->tcp_hdr) {
    }
    if (packet->ip_hdr) {
        struct iphdr* ip_hdr = packet->ip_hdr;
        ip_hdr->tot_len = htons(ip_hdr->ihl * 4 + len);
    }
    memcpy(packet->data, user_data, len);
    packet->len += len;
    return 1;
}
int net_send(char* ifname, Packet_t* packet, uint8_t *user_data, int data_len)
{
    int len;
    struct ifreq ifr;
    struct sockaddr_ll ll_dest;

    net_fill_user_data(packet, user_data, data_len);
    net_checksum(packet);
    int fd = socket_packet_new();
    strcpy(ifr.ifr_name, ifname);
    ioctl(fd, SIOCGIFINDEX, &ifr);
    ll_dest.sll_family = AF_PACKET;
    ll_dest.sll_ifindex = ifr.ifr_ifindex;
    ll_dest.sll_halen = htons(ETH_HLEN);
    len = sendto(fd, packet->buf, packet->len, 0, (struct sockaddr*)&ll_dest, sizeof(ll_dest));
    if (len < 0) {
        log_syserr("net stack send error.");
    }
    close(fd);
    return len;
}


